#TODO: dslc中的macro展开，dslc不提供函数？？

from ..core.dslc_parser import ASTNode, BlockOp, UnaryOp, BinaryOp, Identifier, String
from ..core.dslc_symbol_table import SymbolTable
from ..core.dslc_parser import build_default_parser
from ..core.dslc_lexer import build_default_lexer
from ..core.dslc_symbol_table import build_default_symtab
from ..interpret import evaluation

GLOBAL_MACROS_LIST = []


def macro_define_pass(stab: SymbolTable, top: ASTNode):
    __macro_define(stab, top)
    return top


def is_macro_define_block(top: ASTNode):
    '''判断是不是macro block'''
    if not isinstance(top, BlockOp):
        return False
    env = []
    opslen = len(top.oplist)
    opidx = -1
    while opidx < opslen-1:
        opidx += 1
        op = top.oplist[opidx]
        if isinstance(op, UnaryOp) and op.op == ':':
            if isinstance(op.x, Identifier):
                env.append(op.x.name)
            elif isinstance(op.x, BinaryOp):
                env.append(op.x.l.name+"::"+op.x.r.value)
            else:
                raise ValueError('can not parse COLON UnaryOp')
        else:
            break
    ismacro = False
    for e in env:
        if e.startswith('macro::'):
            ismacro = True
    return ismacro

def __macro_define(stab: SymbolTable, top: ASTNode):
    '''所有的macro都定义在全局'''
    env = []
    opslen = len(top.oplist)
    opidx = -1
    while opidx < opslen-1:
        opidx += 1
        op = top.oplist[opidx]
        if isinstance(op, UnaryOp) and op.op == ':':
            if isinstance(op.x, Identifier):
                env.append(op.x.name)
            elif isinstance(op.x, BinaryOp):
                param = stab.lsvisiable()
                op.x.r.value = op.x.r.value.format_map(param)
                env.append(op.x.l.name+"::"+op.x.r.value)
            else:
                raise ValueError('can not parse COLON UnaryOp')
        else:
            break
    opidx = max(opidx-1, -1)
    macr = None
    for e in env:
        if e.startswith('macro::'):
            macr = MBody(e[7:], True)
    if not macr:
        return
    for e in env:
        if e.startswith('varlist::'):
            macr.params.extend(p.strip() for p in e[9:].split(','))
    stab.add(macr.name, {"mbody": macr})
    global GLOBAL_MACROS_LIST
    GLOBAL_MACROS_LIST.append(macr)
    while opidx < opslen-1:
        opidx += 1
        op = top.oplist[opidx]
        macr.oplist.append(op)


class MBody():
    def __init__(self, n: str, is_meta: bool):
        self.name = n
        self.params = []
        self.oplist = []
        self.is_meta = is_meta

    def __str__(self):
        ret = '\n{\n' + self.name + '\n'
        for op in self.params:
            ret += str(op) + '\n'
        for op in self.oplist:
            ret += str(op) + '\n'
        ret += '}\n'
        return ret
    
    def __repr__(self):
        return str(self)


def merge_marco_param_pass(top: ASTNode):
    global GLOBAL_MACROS_LIST
    if isinstance(top, BinaryOp) and top.op == '!':
        for mbody in GLOBAL_MACROS_LIST:
            if mbody.name == top.l.name:
                return 1
    return 0


def macro_expand_pass(stab: SymbolTable, top: ASTNode):
    if not isinstance(top, BinaryOp):
        raise ValueError('shuold expand wrapper')
    mname = top.l.name
    mbody = stab.lookup(mname).info['mbody']
    paradict = {}
    symtb = build_default_symtab()
    if len(mbody.params) == 1:
        pa = mbody.params[0]
        if isinstance(top.r, Identifier):
            paradict[pa] = top.r.name
            symtb.add(pa, {'ival': "{{{}}}".format(paradict[pa])})
        else:
            paradict[pa] = top.r.value
            symtb.add(pa, {'ival': paradict[pa]})
    else:
        for idx, pa in enumerate(mbody.params):
            if isinstance(top.r.oplist[idx], Identifier):
                paradict[pa] = top.r.oplist[idx].name
                symtb.add(pa, {'ival': "{{{}}}".format(paradict[pa])})
            else:
                paradict[pa] = top.r.oplist[idx].value
                symtb.add(pa, {'ival': paradict[pa]})
    #macro中的bare string构成返回的表达式，其他的表达式需要计算来更新macro中的symtab
    mexprs = []
    mstrings = []
    for st in mbody.oplist:
        if isinstance(st, String):
            stvalue = st.value
            print("mstrings --->", stvalue)
            if mbody.is_meta:
                param = symtb.lsvisiable()
                print('p', param)
                print(stvalue)
                stvalue = stvalue.format_map(param)
            mstrings.append(__append_slash(stvalue))
        else:
            mexprs.append(st)
            if mbody.is_meta:
                print('eval following', st)
                evaluation(symtb, st)
    print(mbody)
    print("mstrings--->", mstrings)
    print(mexprs)
    #print(symtb)
    #
    lexer = build_default_lexer()
    tokens = lexer.tokenize('\n'.join(mstrings))
    parser = build_default_parser(tokens)
    mast = parser.parse()
    print("exp ast----->", mast)
    return mast


def is_macro_expand_block(top: ASTNode):
    if not isinstance(top, BinaryOp):
        return False
    print(top)
    if not isinstance(top.l, Identifier) and not top.op == '!':
        return False
    global GLOBAL_MACROS_LIST
    mnames = [m.name for m in GLOBAL_MACROS_LIST]
    if top.l.name in mnames:
        return True
    return False


def __append_slash(exps: str):
    if len(exps) < 3:
        return exps
    lidx = exps.find('"')
    if lidx == -1:
        return exps
    ridx = exps.rfind('"')
    return exps[:lidx+1] + exps[lidx+1:ridx].replace('"', '\\"') + exps[ridx:]

